#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <ctype.h>

#include "globals.h"
#include "exiterror.h"
#include "fileproc.h"


/*
Genera string conteniendo todos los caracteres que no son alfanuméricos.
La cadena se genera una única vez y se solo se devuelve en las siguientes
llamadas a la función.


devuelve: Puntero a string (esta en stack, es una variable estática).
*/
static const char* get_separator()
{
	static char sep[257] = "";

	int i, pos = 0;

	if (sep[0] == '\0')
	{
		for (i = 0; i < 256; i ++)
		{
			if ( ( (char) i != '\0') && !isalnum (i))
				sep[pos++] = (char) i;
		}

		sep[pos] = '\0';
	}

	return sep;
}


/*
Libera la memoria dinámica ocupada por un string.

1er parámetro: Puntero a la cadena.


devuelve: -.
*/
void free_string (char** str)
{
	if (!str)
		return;

	free (*str);
	*str = NULL;
}


/*
Agrega una nueva palabra a la lista dinámica.

1er parámetro: Puntero a la palabra a agregar.

2do parámetro: Puntero a la lista.

3er parámetro: Puntero a la cantidad de palabras.

4to parámetro: Puntero al tamaño de la lista.


devuelve: -.
*/
static int add_word_to_list (const char* word, char** word_list[], int* word_count, int* list_size)
{
	char* word_copy;
	char** new_word_list = NULL;


	/* Si es necesario hago crecer la lista. */
	if ((*list_size) < (*word_count) + 1)
	{
		new_word_list = realloc (*word_list, ((*list_size) + MIN_WORD_COUNT)*sizeof(char*));

		if (!new_word_list)
			return ERROR_ALLOCATING_MEMORY;

		*word_list = new_word_list;
		*list_size += MIN_WORD_COUNT;
	}

	word_copy = malloc (strlen (word) + 1);

	if (!word_copy)
		return ERROR_ALLOCATING_MEMORY;

	strcpy (word_copy, word);

	(*word_list) [ (*word_count) ++] = word_copy;

	return ERROR_SUCCESS;
}


/*
Separa la cadena pasada en "tokens", palabras en este caso, dado los separadores
que se eligieron.

1er parámetro: Puntero a la cadena.

2do parámetro: Puntero a la lista.

3er parámetro: Puntero a la cantidad de palabras.

4to parámetro: Puntero al tamaño de la lista.
*/
static int tokenize (char* line, char** word_list[], int* word_count, int* list_size)
{
	int ierror;
	char* token = strtok (line, get_separator());

	while (token != NULL)
	{
		/* No acepto tokens vacíos. */
		if (strlen (token))
		{
			if ( (ierror = add_word_to_list (token, word_list, word_count, list_size)) != ERROR_SUCCESS)
			{
				free_word_list (word_list, word_count, list_size);
				return ierror;
			}
		}

		token = strtok (NULL, get_separator());
	}

	return ERROR_SUCCESS;
}


/*
Lee una línea completa sin importar su longitud en memoria dinámica sin incluir el salto de línea.

1er parámetro: flujo de entrada.

2do parámetro: Puntero a la línea.


devuelve: Si la lectura fué exitosa.

*/
static int read_line (FILE* finstream, char** line)
{
	size_t line_len = 0;
	size_t filled_line_len = 0;
	char* newline = NULL;
	bool line_complete = false;

	*line = NULL;


	/*
	Leo tantos caracteres como entren en el buffer, en caso de no entrar la línea completa
	en el mismo, agrando el buffer con "realloc" y continúo leyendo carcateres en el mismo,
	continúo este proceso hasta obtener una línea completa, se acabe el archivo o me quede
	sin memoria suficioente.
	El trabajo de esta función es muy similar a la función "getline" del standard POSIX e
	implementada por GNU, pero se prefirió escribir una versión propia, ya que la mencionada
	no es ANSI C, lo cual se trató de evitar.
	*/
	do
	{
		line_len += MIN_LINE_LEN;

		newline = realloc (*line, line_len);

		if (!newline)
		{
			free_string (line);

			return ERROR_ALLOCATING_MEMORY;
		}

		*line = newline;

		if (!fgets ( (*line) + filled_line_len, line_len - filled_line_len, finstream))
		{
			if (ferror (finstream))
			{
				free_string (line);

				return ERROR_READING_FROM_INPUT_FILE;
			}
		}

		filled_line_len = strlen (*line);

		if (filled_line_len > 0)
		{
			if ( (line_complete = ( (*line) [filled_line_len - 1] == '\n')))
			{
				(*line) [--filled_line_len] = '\0';

				if ( (*line) [filled_line_len - 1] == '\r')
					(*line) [--filled_line_len] = '\0';
			}
		}
	}
	while (!line_complete && (filled_line_len != 0) && !feof (finstream));

	return ERROR_SUCCESS;
}


/*
Construye la lista de palabras a partir de los archivos

1er parámetro: Cantidad de archivos.

2do parámetro: Lista de nombres de archivos terminada en NULL.

3er parámetro: Cantidad de palabras leídas de los archivos.

devuelve: Código de errror.
*/
int make_word_list (int file_count, char* const file_list[], char** word_list[], int* word_count, int* list_size)
{

	FILE* finstream;
	int i;
	char* line = NULL;
	int ierror = ERROR_SUCCESS;

	*word_count = 0;
	*list_size = 0;
	*word_list = NULL;

	/* Proceso todos los archivos pasados, uno a uno. */
	for (i = 0; (ierror == ERROR_SUCCESS) && (i < file_count); i++)
	{
		pprocfilename = file_list[i];

		if ( (finstream = fopen (pprocfilename, "r")) == NULL)
			ierror =  ERROR_CANNOT_OPEN_INPUT_FILE;

		/* Leo el archivo línea a línea hasta llegar al final del mismo o hasta que se produce un error. */
		while ( (ierror == ERROR_SUCCESS) && (!feof (finstream)))
		{
			ierror = read_line (finstream, &line);

			if (ierror == ERROR_SUCCESS)
			{
				/* Separo la línea leída en palabras y las agrego al array de palabras. */
				ierror = tokenize (line, word_list, word_count, list_size);
			}

			free_string (&line);
		}

		/* Si se abrió correctamente el archivo, lo cierro. */
		if (ierror !=  ERROR_CANNOT_OPEN_INPUT_FILE)
			fclose (finstream);
	}

	/* Si se produjo algún error, libero la memoria ocupada por el array de palabras. */
	if (ierror != ERROR_SUCCESS)
		free_word_list (word_list, word_count, list_size);

	/* Devuelvo el código de error. */
	return ierror;
}


/*
Libera la memoria ocupada por la lista de palabras.

1er parámetro: Puntero a la lista.

2do parámetro: Puntero a la cantidad de palabras.

3er parámetro: Puntero al tamaño de la lista.


Devuelve: -.
*/
void free_word_list (char** word_list[], int* word_count, int* list_size)
{
	int i;

	if (!word_list || ! (*word_list))
		return;

	for (i = 0; i < *word_count; i++)
		free_string (& ( (*word_list) [i]));

	free (*word_list);
	*word_list = NULL;
	*list_size = 0;
	*word_count = 0;
}


#ifdef DEBUG
/*
Testea el orden en un array de palabras.

1er parámetro: Array de palabras.

2do parámetro: Cantidad de palabras.


devuelve: Ordenado\No ordenado.
*/
bool order_test(char* words[], int n)
{
        int i;

        for (i = 0; i < n - 1; i++)
                if (compare_str(words[i], words[i + 1]) > 0)
                        return false;

        return true;
}
#endif
